#include "FrameBuffer.h"

#include <glad/glad.h>
#include <SERender/Resourses/Loader/TextureLoader.h>
#include <SEDebug/Log/Log.h>

namespace SEngine
{
    FrameBuffer::FrameBuffer()
    {
        glGenFramebuffers(1, &m_fbo);
        glGenRenderbuffers(1, &m_rbo);
    }

    FrameBuffer::~FrameBuffer()
    {
        glDeleteFramebuffers(1, &m_fbo);
        glDeleteRenderbuffers(1, &m_rbo);
    }

    void FrameBuffer::Resize(int w, int h)
    {
        Bind();
        for (auto & [attachment, tex] : m_attachments) {
            tex.texture->get().Resize(w, h);
        }
        if (m_hasRenderBuffer) {
            glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
            glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
            glBindRenderbuffer(GL_RENDERBUFFER, 0);
        }
    }

    void FrameBuffer::Bind()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    }

    void FrameBuffer::Unbind()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    void FrameBuffer::ClearAttachments()
    {
        m_attachments.clear();
        Bind();
        for (int attachment = Attachment::COLOR_ATTACHMENT0; attachment <= Attachment::COLOR_ATTACHMENT31; ++attachment)
            glFramebufferTexture(GL_FRAMEBUFFER, attachment, 0, 0);
        glFramebufferTexture(GL_FRAMEBUFFER, Attachment::DEPTH_ATTACHMENT, 0, 0);
        glFramebufferTexture(GL_FRAMEBUFFER, Attachment::STENCIL_ATTACHMENT, 0, 0);

    }

    void FrameBuffer::Use()
    {
        Bind();
        if (m_hasRenderBuffer) {
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_rbo);
        } else {
            glDrawBuffer(GL_NONE);
            glReadBuffer(GL_NONE);
        }
        std::vector<uint32_t> attachments;
        for (auto & [attachment, tex] : m_attachments) {
            glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, tex.target, tex.texture->get().GetId(), tex.mipLevels);
            attachments.emplace_back(attachment);
        }
        glDrawBuffers(attachments.size(), attachments.data());
        if(auto state = glCheckFramebufferStatus(GL_FRAMEBUFFER); state != GL_FRAMEBUFFER_COMPLETE) {
            switch (state)
            {
            case GL_FRAMEBUFFER_UNDEFINED: Log::GetInstance().Warn("FRAMEBUFFER_UNDEFINED"); break;
            case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: Log::GetInstance().Warn("FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); break;
            case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: Log::GetInstance().Warn("FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); break;
            case GL_FRAMEBUFFER_UNSUPPORTED: Log::GetInstance().Warn("FRAMEBUFFER_UNSUPPORTED"); break;
            case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: Log::GetInstance().Warn("FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"); break;
            case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: Log::GetInstance().Warn("FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS"); break;
            default: Log::GetInstance().Warn("Framebuffer is not complete!"); break;
            }
        }
    }

    std::optional<std::reference_wrapper<Texture2D>> FrameBuffer::SwapAttachments(Attachment attachment, Texture2D &tex)
    {
        auto out = GetAttachment(attachment);
        m_attachments[attachment] = AttachmentInfo{tex, GL_TEXTURE_2D};
        return out;
    }

    std::optional<std::reference_wrapper<Texture2D>> FrameBuffer::SwapAttachments(Attachment attachment, Texture2D& tex, int textarget, int mipLevels)
    {
        auto out = GetAttachment(attachment);
        m_attachments[attachment] = AttachmentInfo{tex, textarget, mipLevels};
        return out;
    }

    std::optional<std::reference_wrapper<Texture2D>> FrameBuffer::GetAttachment(Attachment attachment)
    {
        if (m_attachments.find(attachment) == m_attachments.end()) {
            return std::nullopt;
        }
        return m_attachments.at(attachment).texture;
    }

} // namespace SEngine

